/*
 * File:    vision_test.c
 * Purpose: Provides a graphical way of testing Algorithm V and other aspects of
 *          the ElectroGladiator protocol.
 */

/*
 Copyright 2009 James Youngquist, Colin Scott.

 This file is part of ElectroGladiator.  ElectroGladiator is free
 software: you can redistribute it and/or modify it under the terms
 of the GNU General Public License as published by  the Free
 Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <math.h>

#include <SDL/SDL.h>
#include <SDL/SDL_gfxPrimitives.h>

#include "vision_test.h"
#include "eg_vision.h"
#include "eg_map.h"
#include "eg_geom.h"

// This should probably be moved inside main eventually...
Map map;

int main(int argc, char **argv) {
    printf("== ElectroGladiators Vision Algorithm Test ==\n");
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <map file.egm>\n", argv[0]);
        exit(1);
    }

    printf("Initializing SDL...");
    /* Init SDL */
    SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);

    /* Set window title */
    SDL_WM_SetCaption("Algorithm V Test", "Algorithm V Test");

    /* Initialize the display */
    SDL_Surface *screen;
    screen = SDL_SetVideoMode(XRES, YRES, DEPTH, SDL_HWSURFACE | SDL_DOUBLEBUF);
    if (!screen) {
        fprintf(stderr, "Can't set %d by %d: %s\n", XRES, YRES, SDL_GetError());
        exit(1);
    }
    printf("Done.\n");

    /* Initialize the map */
    printf("Loading %s...", argv[1]);
    readin_map(argv[1], &map);
    printf("Done.\n");


    /* Validate map */
    if(!validate_map(&map)) {
        fprintf(stderr, "Invalid map data.\n");
        // Uncomment this later ****
        //exit(1);
    }

    printf("Entering main loop.\n");

    SDL_Event event;
    do {
        // Update game state
        game_tick();

        // DRAWING CODE
        /* clear the screen */
        boxColor(screen, 0, 0, XRES - 1, YRES - 1, 0x000000FF);
        // draw map's entities
        draw_map(screen, &map);
        // otherwise get a black screen
        SDL_Flip(screen);
        // END DRAWING CODE

        /* Wait for user input.  That is, when a key is pressed. */
        do {
        	SDL_WaitEvent(&event);
        } while (event.type != SDL_KEYDOWN && event.type != SDL_QUIT);

        if (event.type == SDL_KEYDOWN) {
            if (event.key.keysym.sym == SDLK_ESCAPE) {
                printf("Goodbye, valiant gladiators of the ether!\n");
                break;
            }
            if (event.key.keysym.sym == SDLK_d)
                map.players[0]->bearing
                -= (M_PI / 16);
            if (event.key.keysym.sym == SDLK_a)
                map.players[0]->bearing
                += (M_PI / 16);
            if (event.key.keysym.sym == SDLK_w) {
                map.players[0]->location.x += 4 * cos(map.players[0]->bearing);
                map.players[0]->location.y += 4 * sin(map.players[0]->bearing);
            }
            if (event.key.keysym.sym == SDLK_s) {
                map.players[0]->location.x -= 4 * cos(map.players[0]->bearing);
                map.players[0]->location.y -= 4 * sin(map.players[0]->bearing);
            }
            if (event.key.keysym.sym == SDLK_r) {
                // push rock into map's LL
                Rock *rock = throw_new_rock(map.players[0]);
                ll_push(&(map.rocks), rock);
            }
        }
        if (event.type == SDL_QUIT)
            break;

    } while (1);

    SDL_Quit();
    return 0;
}

/* Returns x coordinate scaled to screen dims */
int p2x(const Point *p) {
    return (int) (p->x / map.width * (XRES - 1));
}

/* Returns y coordinate scaled to screen dims */
int p2y(const Point *p) {
    return (YRES - 1) - (int) (p->y / map.height * (YRES - 1));
}

/* Converts screen y to map y */
double s2m_x(int x) {
    return (double) ((float) x / (XRES - 1) * map.width);
}

/* Converts map y to screen y */
double s2m_y(int y) {
    return map.height - (double) ((float) y / (YRES - 1) * map.height);
}

/* draw a point on the screen */
void draw_p(SDL_Surface *surf, const Point *p) {
    circleColor(surf, p2x(p), p2y(p), 1, POINT_COLOR);
    circleColor(surf, p2x(p), p2y(p), 3, POINT_COLOR);
    return;
}

/* draw a line to the screen */
void draw_l(SDL_Surface *surf, const Line *l, int color) {
    lineColor(surf, p2x(&l->p1), p2y(&l->p1), p2x(&l->p2), p2y(&l->p2), color);
    return;
}

/* draw a trigon to the screen */
void draw_t(SDL_Surface *surf, const Trigon *t) {
    filledTrigonColor(surf, p2x(&t->p1), p2y(&t->p1), p2x(&t->p2), p2y(&t->p2),
                      p2x(&t->p3), p2y(&t->p3), 0x404040FF);
    //trigonColor(surf, p2x(&t->p1), p2y(&t->p1), p2x(&t->p2), p2y(&t->p2), p2x(
    //        &t->p3), p2y(&t->p3), 0x606060FF);
    return;
}

/* draw a rock to the screen */
// Should Rock *r be a const?
void draw_r(SDL_Surface *surf, Rock *r) {
    circleColor(surf, p2x(&r->location), p2y(&r->location),
             p2x(&(Point){R_RAD,0}), ROCK_COLOR);
    return;
}

/* draw a player on the screen */
void draw_pl(SDL_Surface *surf, Player *p) {
    double FL = normalize_angle(p->bearing + M_PI_4);
    double FR = normalize_angle(p->bearing - M_PI_4);
    double max_length = sqrt(map.width * map.width + map.height * map.height)
                        + 2;
    Line *FL_l = make_ray(&p->location, FL, max_length);
    Line *FR_l = make_ray(&p->location, FR, max_length);
    Line *bearing = make_ray(&p->location, p->bearing, 2);

    // Draw visible areas
    Trigon *t;
    while ((t = (Trigon*) ll_pop(&p->vision_triangles))) {
        draw_t(surf, t);
        free_trigon(t);
    }
    Line *l;
    while ((l = (Line*) ll_pop(&p->visible_lines))) {
        draw_l(surf, l, 0x00C000FF);
        free_line(l);
    }

    // Draw player info
    circleColor(surf, p2x(&p->location), p2y(&p->location),
                p2x(&(Point) {P_RAD,0}), PLAYER_COLOR);
    draw_l(surf, bearing, PLAYER_COLOR);
    //draw_l(surf, FL_l, PLAYER_COLOR);
    //draw_l(surf, FR_l, PLAYER_COLOR);

    free(bearing);
    free(FL_l);
    free(FR_l);

    return;
}

/* draws all elements in a map */
void draw_map(SDL_Surface *surf, Map *m) {
    int i; /* generic index */

    /* draw  walls */
    for (i = 0; i < m->num_lines; i++) {
        draw_l(surf, m->lines[i], WALL_COLOR);
    }

    /* draw players */
    for (i = 0; i < m->num_players; i++) {
        Player *p = m->players[i];
        draw_pl(surf, p);
    }

    /* draw rocks?  Or do only the visible ones get drawn? */

    // Let's try an iterator
    Node * curr;
    for (curr = ll_itr_first(&(m->rocks)); curr != NULL; curr = ll_itr_next(curr)) {
        Rock *r = (Rock *) curr->data; // HAHAHA ROCKSTAR!!!!
        draw_r(surf, r);
    }

    return;
}

/* updates game state
 * - player looks always at mouse pointer
 * - moves forward 4 if space pressed
 */
void game_tick() {
	int i;
	for (i = 0; i < map.num_players; i++) {
		update_player(map.players[i]);
	}

    move_rocks(&map);
    return;
}

/**
 * Brings all player data up to date.
 * @param p The player to update.
 */
void update_player(Player *p) {
    // Modify player's visible_lines to contain only lines the player can see.
    algorithm_s(&map, p);

    // Find entities player can see and place into corresponding linked lists.
    find_visible_entities(&map, p);

    return;
}

/**
 * Creates a new rock at Player's location with same bearing as player as though
 * the player had just thrown this rock.
 * @param owner Player that owns this rock
 * @return The newly thrown rock
 */
Rock* throw_new_rock(Player *owner) {
	// Call private function for placing rock ahead of owner
	// What if owner is facing a wall? Then rock is placed exactly on the
	// the wall. --Possible bug--
	Point *p = place_rock(owner);

	// Rocks have the same bearing as their owners at time of throw
	Rock *r = new_rock(p->x, p->y, owner->bearing, owner);

	free_point(p);

	return r;
}

/*
 * Moves all rocks on the map forward by R_SPEED.
 * Also removes rocks that collide with walls.
 */
void move_rocks (Map *m) {
    // Rock Iterator
    Node *curr;
    for (curr = ll_itr_first(&(m->rocks)); curr != NULL; curr = ll_itr_next(curr)) {
        Rock *r = (Rock *) curr->data;

        // Intersection?
        Point *isect = r_collision(rock_path(r), &map);

        if (!isect) {     // No collision
            r->location.x = r->location.x + r->speed * cos(r->bearing);
            r->location.y = r->location.y + r->speed * sin(r->bearing);
        } else {         // Wall collision
            // Remove rock.
            free(r);
            ll_rm_node(&(m->rocks), curr);
            break;
            // But Wait!! What if the rock intercepts several
            // walls? How do I tell which one is closest?
            // The reason I need to know which one is closest
            // is for player collisions, to be implemented later.
            // Later I need to add in a LL with intersections as
            // as elements. If more than one intersection, loop
            // over all intersections and find the closest. Then
            // add this (shortest) path line to the LL described
            // below.
        }

        free(isect);

        // I think we might also need to keep a LL of all lines
        // created by rock's movements on this tick. This would be in order to
        // see if a player runs into one of the rock paths. B/C even if
        // a rock collides with the wall this tick, it's possible that
        // it went through a player's walking path. So even though the
        // rock isn't on the map anymore, we need to somehow keep track of
        // the fact that it hit someone.
    }
}

/*
 * Checks for wall collisions (player collisions too?)
 * for the given rock. Returns the point of collision
 * if one was found, or NULL if no collision occurred.
 */
// This should probably go in some other module, I'm just
// not sure which one.
Point* r_collision (Line *path, Map *m) {
    int i;
    int found = 0;  // boolean flag
    // Needs to be freed *****
    Point *p = calloc(1, sizeof(Point));

    // For now, checks all lines on the map.
    // Optimize later.
    for (i = 0; i < m->num_lines; i++) {
        if( (found = intersection(path, m->lines[i], p)) )
            break;
    }

    free(path);

    if(found) {
        return p;
    } else    {
        free(p);
        return NULL;
    }
}

/*
 * Returns a line with endpoints at the rock's
 * current position, and at the full distance R_SPEED
 * away. This is used for rock/player collision detection.
 */
// Should I make this take a void* instead so that I
// can use it to render player path's as well?
Line* rock_path(const Rock *r) {
    // Needs to be freed ******
    Line *path = calloc(1, sizeof(Line));

    // First point is rock's current location
    path->p1.x = r->location.x;
    path->p1.y = r->location.y;

    // End position
    // This code is a bit redundant, since move_rocks() runs the
    // exact same code.
    path->p2.x = r->location.x + r->speed * cos(r->bearing);
    path->p2.y = r->location.y + r->speed * sin(r->bearing);

    return path;
}
